// 日志落地模块 将格式化的日志字符串输出到指定的位置
// 并且支持落地到不同的位置
// 如标准输出，文件，还有滚动文件（文件按照大小和时间），但是也有其他的方向
// 实现思想 1 抽象出一个基类  2 然后不同的落地方案从基类里面扩展出来  3 使用工厂模式进行创建和表示的分离
#pragma once
#include <memory>
#include <string>
#include <fstream>
#include <sstream>
#include <cassert>
#include "util.hpp"
using namespace std;
namespace LOG // 日志命名空间
{
    class SinkLog // 日志输出器基类（抽象类）
    {
    public:
        using ptr = shared_ptr<SinkLog>;
        SinkLog() {};
        virtual void log(const char *str, size_t len) = 0; // 纯虚函数，输出日志
    };

    class StdoutSink : public SinkLog // 标准输出日志输出器
    {
    public:
        // 写入到标准输出
        void log(const char *str, size_t len)
        {

            cout.write(str, len);
        }
    };

    class FileSink : public SinkLog // 文件日志输出器
    {
    public:
        FileSink(string pathname) // 构造函数，传入文件路径
            : _pathname(pathname)
        {
            // 先创建相应的文件目录
            LOG::util::File::creatDirectory(LOG::util::File::path(pathname));
            // 然后打开
            _ofs.open(_pathname, ios::binary | ios::app); // 以二进制和追加的方式打开
            assert(_ofs.is_open());                       // 断言是否已经打开，不打开的话报错
        }

        // 写入到文件
        void log(const char *str, size_t len)
        {
            // 文件写入逻辑待实现
            _ofs.write(str, len);
            assert(_ofs.good());
        }

    private:
        string _pathname; // 文件路径
        ofstream _ofs;    // 输出流对象
    };
    class RollBysizeSink : public SinkLog // 标准输出日志输出器
    {
    public:
        RollBysizeSink(string &name, size_t size)
            : _name(name),
              max_size(size),
              cur_size(0)
        {
            // 先写一个新的文件名
            string _pathname = createFile();
            // 然后看创建这个文件的路径
            LOG::util::File::creatDirectory(LOG::util::File::path(_pathname));
            // 然后打开
            _ofs.open(_pathname, ios::binary | ios::app); // 以二进制和追加的方式打开
            assert(_ofs.is_open());                       // 断言是否已经打开，不打开的话报错
        }
        // 写入到标准输出
        void log(const char *str, size_t len)
        {
            if (cur_size >= max_size)
            {
                _ofs.close();
                // 先写一个新的文件名
                string _pathname = createFile();
                // 然后看创建这个文件的路径
                count++;
                LOG::util::File::creatDirectory(LOG::util::File::path(_pathname));
                // 然后打开
                _ofs.open(_pathname, ios::binary | ios::app); // 以二进制和追加的方式打开
                assert(_ofs.is_open());
                cur_size = 0; // 断言是否已经打开，不打开的话报错
            }
            _ofs.write(str, len);
            cur_size += len;
        }

    private:
        string createFile()
        {
            struct tm t;
            time_t tm = LOG::util::Data::getime();
            localtime_r(&tm, &t);
            stringstream ss;
            ss << _name;
            ss << t.tm_year + 1900;
            ss << t.tm_mon;
            ss << t.tm_mday;
            ss << t.tm_hour;
            ss << t.tm_min;
            ss << to_string(count);
            ss << ".log";
            return ss.str();
        }

    private:
        string _name;
        ofstream _ofs;
        size_t max_size;
        size_t cur_size;
        int count =0;
    };
    // 扩展一个为时间滚动的模块
    // time()取整 时间间隔 ->假设我们刚开始的时候结果为11，然后过了一会为12，这也意味着，已经不是一个时间间隔了，此时我们需要切换文件


    class RollBytimeSink : public SinkLog
    {
    public:
        RollBytimeSink(const std::string &name, int size)
            : _pathname(name),
              jump(size)
        {
            // 先写一个新的文件名
            string pathname = createFile();
            // 然后看创建这个文件的路径
            LOG::util::File::creatDirectory(LOG::util::File::path(pathname));
            // 然后打开
            _ofs.open(pathname, ios::binary | ios::app); // 以二进制和追加的方式打开
            assert(_ofs.is_open());                       // 断言是否已经打开，不打开的话报错
            time = LOG::util::Data::getime();
            n = time / jump;
        }
        // 写入到标准输出
        void log(const char *str, size_t len)
        {
            if (LOG::util::Data::getime() / jump > n)
            {
                _ofs.close();
                // 先写一个新的文件名
                string _pathname = createFile();
                // 然后看创建这个文件的路径
                count++;
                LOG::util::File::creatDirectory(LOG::util::File::path(_pathname));
                // 然后打开
                _ofs.open(_pathname, ios::binary | ios::app); // 以二进制和追加的方式打开
                assert(_ofs.is_open());
                n = LOG::util::Data::getime() % jump;
            }
            _ofs.write(str, len);
        }

    private:
        string createFile()
        {
            struct tm t;
            time_t tm = LOG::util::Data::getime();
            localtime_r(&tm, &t);
            stringstream ss;
            ss << _pathname;
            ss << t.tm_year + 1900;
            ss << t.tm_mon;
            ss << t.tm_mday;
            ss << t.tm_hour;
            ss << t.tm_min;
            ss << to_string(count);
            ss << ".log";
            return ss.str();
        }

    private:
        int count =0;
        string _pathname;
        ofstream _ofs;
        time_t time;
        time_t jump;
        size_t n;
    };



    class SinkFactory
    {
    public:
        template <typename Type, typename... Args>
        static LOG::SinkLog::ptr Create(Args &&...args)
        {
            return std::make_shared<Type>(std::forward<Args>(args)...);
        }
    };

}
